package com.example.practiceopenglforyuv;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * name : GLSurfaceView.Renderer
 * time : 2019.11.7
 */

public class MGLRenderer implements GLSurfaceView.Renderer {

    /**
     * Global Variable
     *
     * create a renderer for surface
     */
    // for log
    private static final String tag = "_" + "MGLRenderer";
    // for surface view
    private GLSurfaceView mSurface;
    // for image
    private int mWidth, mHeight;
    private ByteBuffer y, u, v;
    // for camera
    private String mCameraId;

    /**
     * 构造函数，参数初始化
     * @param surface   对应的GLSurfaceView
     */
    MGLRenderer(GLSurfaceView surface) {
        mSurface = surface;
        mProgram = 0;
        mWidth = 0;
        mHeight = 0;

        y = null;
        u = null;
        v = null;
    }

    /**
     * 获取CameraId，以确定不同的图像变换
     * @param cameraId  摄像头编号
     */
    void setCameraId(String cameraId) {
        synchronized (this) {
            mCameraId = cameraId;
            Log.d(tag, "set camera id : =Image= " + cameraId);
        }

    }

    /**
     * 设置ByteBuffer大小
     * @param w 宽
     * @param h 高
     */
    void setWidthAndHeight(int w, int h) {
        if (w > 0 && h > 0) {
            if (w != mWidth && h != mHeight) {
                mWidth = w;
                mHeight = h;
                int ySize = w * h;
                int uvSize = ySize / 4;
                synchronized (this) {
                    y = ByteBuffer.allocate(ySize);
                    u = ByteBuffer.allocate(uvSize);
                    v = ByteBuffer.allocate(uvSize);
                }
            }
        }
    }

    /**
     * 三平面图像数据输入
     * @param yBytes    y平面
     * @param uBytes    u平面
     * @param vBytes    v平面
     */
    void updateImage(byte[] yBytes, byte[] uBytes, byte[] vBytes) {
        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();
            y.put(yBytes, 0, yBytes.length);
            u.put(uBytes, 0, uBytes.length);
            v.put(vBytes, 0, vBytes.length);
        }
        mSurface.requestRender();
    }

    /**
     * NV21图像数据输入
     * @param nv21Bytes Byte[]形式的NV21图像数据
     */
    void updateImage(byte[] nv21Bytes) {
        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();
            y.put(nv21Bytes, 0, y.capacity());
            int size = mWidth * mHeight;
            for (int i=0; i<size/4; i++) {
                v.put(i, nv21Bytes[size+i*2+1]);
                u.put(i, nv21Bytes[size+i*2]);
            }
        }
        mSurface.requestRender();
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
//        Log.d(tag, "on surface created");
        GLES20.glClearColor(0.0f, .0f, 0.0f, 1.0f); // black
        buildProgram();
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int i, int i1) {
//        Log.d(tag, "on surface changed");
        GLES20.glViewport(0, 0, i, i1);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //
        Log.d(tag, "on draw frame");
        synchronized (this) {
            if (y != null) {
//                Log.d(tag, "y is not null");
                y.position(0);
                u.position(0);
                v.position(0);
                buildTextures();
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
                drawFrame();
            }
        }

    }

    /**
     * 构建着色器程序，编译、链接
     */
    private void buildProgram() {
        // create buffers
        createBuffers();
        // create program
        createProgram();
        // get handles
        getHandle();
    }

    /**
     * 构建纹理平面
     */
    private void buildTextures() {
        // set rotation matrix
        float angle;
        Log.d(tag, "set camera id : =angle= " + mCameraId);
        if (mCameraId.equals("1")) {
            float[] rotateMatrix = new float[16];
            float[] mirrorMatrix = new float[16];
            angle = -90;
            Matrix.setRotateM(rotateMatrix, 0, angle, 0, 0, -1);
            angle = 180;
            Matrix.setRotateM(mirrorMatrix, 0, angle, 1, 0, 0);
            Matrix.multiplyMM(mvpMatrix, 0, rotateMatrix, 0, mirrorMatrix, 0);
        } else {
            angle = 90;
            Matrix.setRotateM(mvpMatrix, 0, angle, 0, 0, -1);
        }
        // build texture for y
        if (yTextureId <= 0) {
            int[] textures = new int[1];
            // 生成一个纹理id，此时对应的纹理还没有指定维度
            // 产生贴图对象id
            GLES20.glGenTextures(1, textures, 0);
            yTextureId = textures[0];
        }
        // 将一个纹理id绑定到一个纹理目标，GLES20.GL_TEXTURE_2D，该纹理为二维纹理，确定了纹理维度
        // 绑定贴图对象到贴图目标上
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yTextureId);
        // 指定一个二维纹理的纹理图片，即向纹理目标添加图像数据
        // 使用贴图装载实际的图像数据
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mWidth, mHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y);
        // 设置纹理参数，纹理缩小功能设置，临近采样
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        // 设置纹理参数，纹理放大功能设置，线性采样
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        // 设置纹理参数，纹理x方向拉伸功能设置，截取拉伸
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        // 设置纹理参数，纹理y方向拉伸功能设置，截取拉伸
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        // build texture for u
        if (uTextureId <= 0) {
            int[] textures = new int[1];
            GLES20.glGenTextures(1, textures, 0);
            uTextureId = textures[0];
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, uTextureId);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mWidth/2, mHeight/2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, u);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        // build texture for v
        if (vTextureId <= 0) {
            int[] textures = new int[1];
            GLES20.glGenTextures(1, textures, 0);
            vTextureId = textures[0];
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, vTextureId);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mWidth/2, mHeight/2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, v);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    }

    /**
     * 传入着色器程序参数，激活纹理器
     */
    private void drawFrame() {
        // use program
        GLES20.glUseProgram(mProgram);  // 设置项目为实际的渲染目标，激活项目
        // set program's params
        // 指定顶点属性-位置属性
        GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 8, rectBuffer); // 顶点坐标设置顶点位置
        GLES20.glEnableVertexAttribArray(positionHandle);
        // 指定顶点属性-贴图坐标
        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, textBuffer);  // 纹理坐标设置纹理位置
        GLES20.glEnableVertexAttribArray(textureHandle);
        // active textures
        // 激活贴图单元
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);             // 激活纹理单元（GLES20.GL_TEXTURE0）
        // 为当前贴图单元绑定贴图对象
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yTextureId); // 将纹理id绑定激活的纹理单元，使得纹理单元与纹理目标对应
        // 为片段着色器中的y采样器指定已经绑定过的贴图单元0
        GLES20.glUniform1i(yHandle, 0);                      // 将激活的纹理单元0对应的纹理目标内内的图像数据添加到变量yHandle中
        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, uTextureId);
        GLES20.glUniform1i(uHandle, 1);                      // 装载常量
        GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, vTextureId);
        GLES20.glUniform1i(vHandle, 2);
        // set rotation matrix
        GLES20.glUniformMatrix4fv(matrixHandle, 1, false, mvpMatrix, 0);
        // get rgb texture
        // draw frame
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//        GLES20.glFinish();
        //
        GLES20.glDisableVertexAttribArray(positionHandle);
        GLES20.glDisableVertexAttribArray(textureHandle);
    }

    /**
     * Global Variable
     *
     * create a program and several shader codes
     */
    // for program
    private int mProgram;
    // for shader codes
    private int positionHandle;
    private int textureHandle;
    private int matrixHandle;
    private int yHandle;
    private int uHandle;
    private int vHandle;
    // for building textures
    private int yTextureId;
    private int uTextureId;
    private int vTextureId;
    // for rotation matrix
    private float[] mvpMatrix = new float[16];
    // for coordinates
    private ByteBuffer rectBuffer;  // 存储顶点坐标的缓冲区
    private ByteBuffer textBuffer;  // 存储纹理坐标的缓冲区
    // for rectangle vertex coordinate
    private static float[] rectVertices = {
            -1.0f,-1.0f,
            1.0f,-1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f
    };
    // for texture vertex coordinate
    private static float[] textVertices = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f
    };
    // for vertex shader code
    private static final String vertexShaderCode =
            "attribute vec4 vPosition;" +
            "attribute vec2 textCoord;" +
            "uniform mat4 uMVPMatrix;" +
            "varying vec2 tc;" +            // 纹理坐标
            "void main() {" +
            "   gl_Position = uMVPMatrix * vPosition;" +
            "   tc = textCoord;" +
            "}";
    // for fragment shader code
    private static final String fragmentShaderCode =
            "precision mediump float;" +            // 精度
            "uniform sampler2D text_y;" +    // y纹理采样器
            "uniform sampler2D text_u;" +    // u纹理采样器
            "uniform sampler2D text_v;" +    // v纹理采样器
            "varying vec2 tc;" +
            "void main() {" +
            "   mediump vec3 yuv;" +
            "   lowp vec3 rgb;" +
            "   yuv.x = texture2D(text_y, tc).r;" +
            "   yuv.y = texture2D(text_v, tc).r - 0.5;" +
            "   yuv.z = texture2D(text_u, tc).r - 0.5;" +
            "   rgb = mat3(1, 1, 1, 0, -0.39465, 2.03211, 1.13983, -0.58060, 0) * yuv;" +
            "   gl_FragColor = vec4(rgb, 1);" +
            "}";

    /**
     * 为顶点坐标和纹理坐标，创建缓冲区
     */
    private void createBuffers() {
        // for rectangle
        /*
        下面ByteBuffer的两个静态函数，都是为ByteBuffer类对象分配缓冲区的
        不同的是，创建direct的缓冲区（操作系统内存），会提升该对象参与IO操作的性能
         */
//        rectBuffer = ByteBuffer.allocate(rectVertices.length * 4);
        rectBuffer = ByteBuffer.allocateDirect(rectVertices.length * 4);
        /*
        下面ByteBuffer的方法是设置ByteBuffer的字节序
        ByteOrder.BIG_ENDIAN    大字节序
        ByteOrder.LITTLE_ENDIAN 小字节序
        ByteOrder.nativeOrder() 返回当前硬件平台的字节序
         */
        rectBuffer.order(ByteOrder.nativeOrder());
        /*
        将ByteBuffer设置为FloatBuffer，并写入数据
         */
        rectBuffer.asFloatBuffer().put(rectVertices);
        /*
        设置接下来需要读写操作的位置
        初始化时读写位置为0，但上面进行了一次写入操作，位置改变，需要重新设置
         */
        rectBuffer.position(0);
        // for textures
        textBuffer = ByteBuffer.allocateDirect(textVertices.length * 4);
        textBuffer.order(ByteOrder.nativeOrder());
        textBuffer.asFloatBuffer().put(textVertices);
        textBuffer.position(0);
    }

    /**
     * 创建着色器程序
     */
    private void createProgram() {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();                // 创建项目句柄
        if (mProgram != 0) {
            GLES20.glAttachShader(mProgram, vertexShader);  // 将着色器添加到项目中
            GLES20.glAttachShader(mProgram, fragmentShader);// 同上
            GLES20.glLinkProgram(mProgram);                 // 链接项目
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] == 0) {
                Log.e(tag, "link program fail !");
                GLES20.glDeleteProgram(mProgram);
                mProgram = 0;
            }
        } else {
            Log.e(tag, "create program fail !");
        }
    }

    /**
     * 加载着色器
     * @param shaderType    着色器类型
     * @param shaderCode    着色器程序
     * @return              着色器编号
     */
    private int loadShader(int shaderType, String shaderCode) {
        int shader = GLES20.glCreateShader(shaderType); // 创建着色器句柄
        if (shader != 0) {
            GLES20.glShaderSource(shader, shaderCode);  // 替换着色器中的源代码
            GLES20.glCompileShader(shader);             // 编译新替换的源代码
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                Log.e(tag, "compile shader fail !");
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        } else {
            Log.e(tag, "create shader fail !");
        }
        return shader;
    }

    /**
     * 获取着色器程序参数操作句柄，用于为这些参数赋值
     */
    private void getHandle() {
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        textureHandle = GLES20.glGetAttribLocation(mProgram, "textCoord");
        matrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        yHandle = GLES20.glGetUniformLocation(mProgram, "text_y");  // 通过常量名称，获取项目里的常量地址
        uHandle = GLES20.glGetUniformLocation(mProgram, "text_u");
        vHandle = GLES20.glGetUniformLocation(mProgram, "text_v");
    }


}

